#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   CAN&PICU网络层测试_boot_test.py
@Time    :   2025/03/20 13:31:28
@Author  :   YangLiang 
@Version :   1.0
@Contact :   yangl@genvict.com
@License :   (C)Copyright 2022-2025
@Desc    :   CAN&PICU网络层测试_BOOT
'''

from pandas._libs import interval
import pytest
import allure
import time
from pytest_assume.plugin import assume
import Set_up
from commDriver.TS_CAN import *

class Test_CAN_PICU_NetworkLayer_BOOT:
    
    @allure.severity('critical')
    @allure.title('监控BOOT程序中的多帧应答报文,检查N_Bs的时间')
    @allure.description('监控BOOT程序中的多帧应答报文,检查N_Bs的时间')
    def test_boot_multi_frame_answer_check_N_Bs_time(self, action, Driver):
        """
        测试CAN&PICU网络层BOOT多帧应答报文,检查N_Bs的时间
        Args:
            action: 测试动作对象
            Driver: 包含CAN驱动的测试驱动对象
        """
        iStep = 1
        # 拦截流控帧发送
        Set_up.is_block_flow_control = True
        # 步骤1：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        # 发送多帧请求
        iStep = Driver['CAN'].sendFrame("03 22 F1 87", iStep)
        with allure.step(f"第{iStep}步:等待165ms # NA"):
            iStep += 1
            time.sleep(0.165)
        # 解除流控帧发送拦截
        Set_up.is_block_flow_control = False
        # 发送流控帧
        flow_ctrl = [0x30, 0x00, 0x00] + [0x00]*5  # BS=0, STmin=0
        iStep, uds_resp, _ = Driver['CAN'].Send_And_Receive(Driver, flow_ctrl, iStep)
        # 拦截流控帧
        Set_up.is_block_flow_control = True
        with allure.step(f"第{iStep}步:判断是否有后续帧返回 # 无后续帧返回"):
            assert uds_resp == [], logging.error(f"发送流控帧后,有返回后续帧,返回值为{uds_resp}")
            iStep += 1
        # 解除流控帧拦截
        Set_up.is_block_flow_control = False


    @allure.severity('critical')
    @allure.title('监控工具对BOOT的多帧请求报文，检查N_Cr的时间')
    @allure.description('监控工具对BOOT的多帧请求报文，检查N_Cr的时间')
    def test_boot_multi_frame_request_check_N_Cr_time(self, action, Driver):
        """
        测试CAN&PICU网络层BOOT多帧请求报文,检查N_Cr的时间
        Args:
            action: 测试动作对象
            Driver: 包含CAN驱动的测试驱动对象
        """
        iStep = 1
        
        # 步骤1：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "3E 00", "7E 00", 2, iStep, True, 'UDS')
        time.sleep(1)
        # 步骤4：发送首帧请求（构造超过7字节的长请求）
        # 原始数据（包含服务ID 2E和参数）
        payload = [0x2E, 0xF1, 0x84] + list(range(0x01, 0x11))  # 总长度16字节
        # 构造首帧（前2字节为PCI类型+长度）
        total_length = len(payload)
        first_frame = [0x10 | (total_length >> 8),  # PCI类型(1) + 长度高字节
            total_length & 0xFF,          # 长度低字节
        ] + payload[0:6]  # 首帧携带前6字节
        
        # 构造连续帧数据（剩余16字节）
        consecutive_data = payload[6:]
        iStep, flow_ctrl, _ = Driver['CAN'].Send_And_Receive(Driver, first_frame, iStep)
        
        # 验证流控帧格式（假设收到单帧流控）
        with assume: 
            assert flow_ctrl[0] == 0x30  # 流控帧类型
            assert flow_ctrl[1] == 0x00  # 块大小（无限制）
            assert flow_ctrl[2] == 0x00  # STmin=0ms
            assert flow_ctrl[3] == 0xAA  # 填充验证
            
        # 步骤5：发送部分连续帧（正确拆分数据）
        # 将剩余数据按7字节分组
        chunks = [consecutive_data[i:i+7] for i in range(0, len(consecutive_data), 7)]
        
        # 发送第一个连续帧后等待165ms发送第二个连续帧
        for seq, chunk in enumerate(chunks[:2], start=1):
            # 构造连续帧PCI：0x20 + 序列号
            cont_frame = [0x20 + seq] + chunk
            # 填充剩余字节为0xAA
            cont_frame += [0xAA] * (8 - len(cont_frame))
            # 发送连续帧
            Driver['CAN'].TransmitData(Set_up.reqID, cont_frame, Set_up.CAN_Type, True)
            # 等待165ms
            time.sleep(0.165)

        
        raw_frames = Driver['CAN'].recv_frame_by_id(Driver, Set_up.resID, 3)
        if len(raw_frames) > 0:
            uds_resp = []
            for frame in raw_frames:
                uds_resp.extend(frame['raw_data'])  # 合并所有原始数据

            respstr = binascii.b2a_hex(bytearray(uds_resp), ' ')
        with assume: assert len(raw_frames) == 0, logging.error(f"实际响应报文：{respstr}")


    @allure.severity('critical')
    @allure.title('监控BOOT中的多帧应答报文，检查(N_Cs+ N_As)的时间')
    @allure.description('监控BOOT中的多帧应答报文，检查(N_Cs+ N_As)的时间')
    def test_boot_multi_frame_response_reception(self, action, Driver):
        iStep = 1
        Set_up.is_block_flow_control = True
        # 步骤1：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        time.sleep(1)    
        # 步骤2：发送需要多帧响应的请求
        start_time = time.time()
        iStep, uds_resp, _ = Driver['CAN'].Send_And_Receive(Driver, "03 22 F1 87", iStep, isRecv=False)
        # 验证首帧响应
        with assume: 
            assert uds_resp[0] == 0x10, logging.error("未收到首帧响应")
            total_length = (uds_resp[0] & 0x0F) << 8 | uds_resp[1]
            logging.info(f"多帧响应总长度：{total_length}字节")

        # 恢复流控帧发送
        Set_up.is_block_flow_control = False
        # 步骤3：发送BS=0的流控帧
        flow_ctrl = [0x30, 0x00, 0x00] + [0x00]*5  # BS=0, STmin=0
        Driver['CAN'].TransmitData(Set_up.reqID, flow_ctrl, Set_up.CAN_Type, True)
        # 拦截流控帧自动发送
        Set_up.is_block_flow_control = True
        
        # 接收所有连续帧并验证时间间隔
        raw_frames = []
        last_frame_time = time.time()
        N_Cr = 140  # N_Cr超时时间(ms)

        # 验证接收到的帧数量是否符合预期
        framesCount = (total_length - 6 + 6) // 7  # 计算预期连续帧数量
        raw_frames,_ = Driver['CAN'].recv_frame_by_id_continuous(Driver, Set_up.resID, framesCount, N_Cr/1000)
        current_time = time.time()    
        interval = (current_time - last_frame_time) * 1000  # 转换为毫秒
        logging.info(f"连续帧接收间隔: {interval:.2f}ms")
        with assume: 
            assert interval <= N_Cr, logging.error(f"连续帧接收超时: {interval:.2f}ms > {N_Cr}ms(N_Cr)")
        with assume: 
            assert len(raw_frames) >= framesCount, logging.error(f"接收帧数量不足: 实际{len(raw_frames)}, 预期{framesCount}")
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False

    @allure.severity('critical')
    @allure.title('监控BOOT中的多帧请求报文，检查(N_Br+ N_Ar)的时间')
    @allure.description('监控BOOT中的多帧请求报文，检查(N_Br+ N_Ar)的时间')
    def test_boot_multi_frame_request_reception(self, action, Driver):
        iStep = 1
        payload = [0x2E, 0xF1, 0x84] + list(range(1, 20))  # 总长度22字节
        # 步骤1：进入编程会话,发送多帧请求
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, payload, "6E F1 84", 2, iStep, True)


    @allure.severity('critical')
    @allure.title('监控BOOT中的多帧应答报文，调整测试工具的流控帧数据块大小为0、帧最小间隔时间为非0')
    @allure.description('监控BOOT中的多帧应答报文，调整测试工具的流控帧数据块大小为0、帧最小间隔时间为非0')
    def test_boot_multi_frame_response_reception_2(self, action, Driver):
        iStep = 1
        stMin = [5, 10, 15, 30, 40]
        flow_ctrl = [0x30, 0x00, 0x00] + [0x00]*5  # BS=0, STmin=0
        Set_up.is_block_flow_control = True
        # 步骤1：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        time.sleep(1)    
        # 步骤2：发送需要多帧响应的请求
        for i in range(5):
            iStep, uds_resp, _ = Driver['CAN'].Send_And_Receive(Driver, "03 22 F1 87", iStep, isRecv=False)
            # 验证首帧响应
            with assume: 
                assert uds_resp[0] == 0x10, logging.error("未收到首帧响应")
                total_length = (uds_resp[0] & 0x0F) << 8 | uds_resp[1]
                logging.info(f"多帧响应总长度：{total_length}字节")
            framesCount = (total_length - 6 + 6) // 7  # 计算预期连续帧数量
            # 恢复流控帧发送
            Set_up.is_block_flow_control = False
            # 发送流控帧
            flow_ctrl[2] = stMin[i]
            Driver['CAN'].TransmitData(Set_up.reqID, flow_ctrl, Set_up.CAN_Type, True)
            # 拦截流控帧发送
            Set_up.is_block_flow_control = True
            # 接收响应
            raw_frames, frameAtimeus = Driver['CAN'].recv_frame_by_id_continuous(Driver, Set_up.resID, framesCount)
            # 计算时间间隔
            if len(frameAtimeus) > 1:
                for j in range(1, len(frameAtimeus)):
                    interval = (frameAtimeus[j] - frameAtimeus[j-1]) * 1000  # 转换为毫秒
                    logging.info(f"连续帧接收间隔: {interval:.2f}ms")
                    with assume: 
                        assert interval >= stMin[i], logging.error(f"连续帧接收超时: {interval:.2f}ms < {stMin[i]}ms(STmin)")
            else:
                logging.error("未收到足够的连续帧")
            # 验证数据长度
            data_length = len(raw_frames)
            with assume: 
                assert data_length == framesCount, logging.error(f"数据长度错误: 实际{data_length}, 预期{framesCount}")
            
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False
    
    @allure.severity('critical')
    @allure.title('监控BOOT中的多帧应答报文，调整测试工具的流控帧数据块大小为非0、帧最小间隔时间为0')
    @allure.description('监控BOOT中的多帧应答报文，调整测试工具的流控帧数据块大小为非0、帧最小间隔时间为0')
    def test_boot_multi_frame_response_reception_3(self, action, Driver):
        iStep = 1
        # 拦截流控帧
        Set_up.is_block_flow_control = True
        # 步骤1：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        time.sleep(1)    
        # 步骤2：发送需要多帧响应的请求
        iStep, uds_resp, _ = Driver['CAN'].Send_And_Receive(Driver, "03 22 F1 87", iStep, isRecv=False)
        # 验证首帧响应
        with assume: 
            assert uds_resp[0] == 0x10, logging.error("未收到首帧响应")
            total_length = (uds_resp[0] & 0x0F) << 8 | uds_resp[1]
            logging.info(f"多帧响应总长度：{total_length}字节")
        framesCount = (total_length - 6 + 6) // 7  # 计算预期连续帧数量
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False
        # 发送流控帧
        flow_ctrl = [0x30, 0x01, 0x00] + [0x00]*5  # BS=0, STmin=0
        Driver['CAN'].TransmitData(Set_up.reqID, flow_ctrl, Set_up.CAN_Type, True)
        # 拦截流控帧发送
        Set_up.is_block_flow_control = True
        # 接收响应
        raw_frames, frameAtimeus = Driver['CAN'].recv_frame_by_id_continuous(Driver, Set_up.resID, framesCount, timeout = 0.1)
        # 验证数据长度
        data_length = len(raw_frames)
        with assume: 
            assert data_length == 1, logging.error(f"数据长度错误: 实际{data_length}, 预期{1}")
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False
        Driver['CAN'].TransmitData(Set_up.reqID, flow_ctrl, Set_up.CAN_Type, True)
        # 拦截流控帧发送
        Set_up.is_block_flow_control = True
        # 接收响应
        raw_frames, frameAtimeus = Driver['CAN'].recv_frame_by_id_continuous(Driver, Set_up.resID, framesCount, timeout = 0.1)
        # 验证数据长度
        data_length = len(raw_frames)
        with assume: 
            assert data_length == 1, logging.error(f"数据长度错误: 实际{data_length}, 预期{1}")
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False
    
    @allure.severity('critical')
    @allure.title('监控BOOT中的多帧应答报文,验证连续帧报文sn正确,帧间隔大于0ms')
    @allure.description('监控BOOT中的多帧应答报文,验证连续帧报文sn正确,帧间隔大于0ms')
    def test_boot_multi_frame_response_reception_4(self, action, Driver):
        iStep = 1
        # 拦截流控帧
        Set_up.is_block_flow_control = True
        # 步骤1：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        time.sleep(1)    
        # 步骤2：发送需要多帧响应的请求
        iStep, uds_resp, _ = Driver['CAN'].Send_And_Receive(Driver, "03 22 F1 87", iStep, isRecv=False)
        # 验证首帧响应
        with assume: 
            assert uds_resp[0] == 0x10, logging.error("未收到首帧响应")
            total_length = (uds_resp[0] & 0x0F) << 8 | uds_resp[1]
            logging.info(f"多帧响应总长度：{total_length}字节")
        framesCount = (total_length - 6 + 6) // 7  # 计算预期连续帧数量
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False
        # 发送流控帧
        flow_ctrl = [0x30, 0x00, 0x00] + [0x00]*5  # BS=0, STmin=0
        Driver['CAN'].TransmitData(Set_up.reqID, flow_ctrl, Set_up.CAN_Type, True)
        # 拦截流控帧发送
        Set_up.is_block_flow_control = True
        # 接收响应
        raw_frames, frameAtimeus = Driver['CAN'].recv_frame_by_id_continuous(Driver, Set_up.resID, framesCount)
        # 验证数据长度
        data_length = len(raw_frames)
        with assume: 
            assert data_length == framesCount, logging.error(f"数据长度错误: 实际{data_length}, 预期{framesCount}")
        # 验证连续帧sn是否正确
        for i in range(len(raw_frames)):
            with assume:
                assert raw_frames[i][0] & 0x0F == i + 1, logging.error(f'第{i}个连续帧sn应该是{i + 1},实际是{raw_frames[i][0] & 0x0F}')
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False

    @allure.severity('critical')
    @allure.title('监控BOOT中的多帧应答报文,ECU返回连续帧个数与时间间隔符合要求')
    @allure.description('监控BOOT中的多帧应答报文,ECU返回连续帧个数与时间间隔符合要求')
    def test_boot_multi_frame_response_reception_5(self, action, Driver):
        iStep = 1
        # 拦截流控帧
        Set_up.is_block_flow_control = True
        # 步骤1：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        time.sleep(1)    
        # 步骤2：发送需要多帧响应的请求
        iStep, uds_resp, _ = Driver['CAN'].Send_And_Receive(Driver, "03 22 F1 87", iStep, isRecv=False)
        # 验证首帧响应
        with assume: 
            assert uds_resp[0] == 0x10, logging.error("未收到首帧响应")
            total_length = (uds_resp[0] & 0x0F) << 8 | uds_resp[1]
            logging.info(f"多帧响应总长度：{total_length}字节")
        framesCount = (total_length - 6 + 6) // 7  # 计算预期连续帧数量
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False
        # 发送流控帧
        flow_ctrl = [0x30, 0x02, 0x14] + [0x00]*5  # BS=0, STmin=0
        Driver['CAN'].TransmitData(Set_up.reqID, flow_ctrl, Set_up.CAN_Type, True)
        # 拦截流控帧发送
        Set_up.is_block_flow_control = True
        # 接收响应
        raw_frames, frameAtimeus = Driver['CAN'].recv_frame_by_id_continuous(Driver, Set_up.resID, framesCount)
        # 验证数据长度
        data_length = len(raw_frames)
        with assume: 
            assert data_length == framesCount, logging.error(f"数据长度错误: 实际{data_length}, 预期{framesCount}")
        # 验证时间间隔
        for i in range(len(frameAtimeus) - 1):
            interval = (frameAtimeus[i + 1] - frameAtimeus[i])* 1000
            logging.info(f"第{i}个连续帧与第{i + 1}个连续帧时间间隔：{interval}ms")
            with assume:
                assert  interval >= 0x14, logging.error(f"时间间隔错误: 实际{interval}ms, 预期20ms")
        # 恢复流控帧发送
        Set_up.is_block_flow_control = False


    @allure.severity('critical')
    @allure.title('监控BOOT中应响应正确的寻址CANID')
    @allure.description('监控BOOT中应响应正确的寻址CANID')
    def test_boot_response_correct_addressing_canid(self, action, Driver):
        iStep = 1
        # 步骤1：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        time.sleep(1)    
        # 步骤2：响应正确的物理寻址ID
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 03", "50 03", 0, iStep, False)
        # 步骤3：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        time.sleep(1)    
        # 步骤4：不响应错误的寻址ID
        iStep, uds_resp, _ = Driver['CAN'].Send_And_Receive(Driver, "10 03", iStep, reqID = 0x751)
        with assume:
            assert uds_resp == [], logging.error(f"应无响应,实际响应{uds_resp}")
        # 步骤5：进入编程会话
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 02", "50 02", 0, iStep, False)
        # 步骤4：响应正确的功能寻址ID
        iStep, _, _ = Driver['CAN'].checkReqResp(Driver, "10 03", "50 03", 0, iStep, False, 'UDSFun')







    





    







    